package org.replay.lib;

/*
 * Copyright © 2020 lizicong,All rights reserved.
 *
 * Licensed under the GPL License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @Description: 分布式系统全局唯一ID递增生成算法
 * 大致原理：根据zoneID/machineID/当前时间戳/单位时间内递增的序列号，这四个参数作为最终ID生成因子，此算法理论上达到近420w的并发量。
 * @HowTOUse 消费者传入zoneID和machineID到有参构造器生成对象，然后消费者无脑调用getID()即可获得全局唯一ID。
 * <br/>
 * @Author: lizicong <845634109@qq.com>
 * @Date: 2020/6/5
 */
public class SnowFlake {
    private static final long zoneBits = 5;
    private static final long machineBits = 5;
    private static final long sequenceBits = 12;

    private static final long maxZoneID = (1 << zoneBits) - 1;
    private static final long maxMachineID = (1 << machineBits) - 1;
    private static final long maxSequenceID = (1 << sequenceBits) - 1;

    private long zoneID;
    private long machineID;
    private long sequenceID = -1L;

    private long lastTimeStamp = -1L;

    public SnowFlake(long zoneID, long machineID) {
        if (zoneID < 0 || zoneID > maxZoneID)
            throw new IllegalArgumentException("zoneID can't be greater than " + maxZoneID + " or less than 0");

        if (machineID < 0 || machineID > maxMachineID)
            throw new IllegalArgumentException("machineID can't be greater than " + maxMachineID + " or less than 0");

        this.zoneID = zoneID;
        this.machineID = machineID;
    }


    /**
     * @return 获取当前时间戳
     */
    private long getCurrentTimeStamp() {
        return System.currentTimeMillis();
    }

    /**
     * @return 在当前线程在单位时间内递增的序列号达到最大时，调用该阻塞方法获取新的时间戳
     */
    private long blockUntilNextMS() {
        long tempTimeStamp = getCurrentTimeStamp();
        while (tempTimeStamp <= lastTimeStamp)
            tempTimeStamp = getCurrentTimeStamp();
        return tempTimeStamp;
    }

    /**
     * @return 生成分布式有序的全局唯一ID
     * @throws Exception 诱因不明，和算法无关所以暂不过多考虑
     */
    public synchronized long getID() {

        long currentTimeStamp = getCurrentTimeStamp();

        //最新时间戳小于最后时间戳的情况
        if (currentTimeStamp < lastTimeStamp)
            throw new RuntimeException("unknow exception");

        //在更新时间后，如果最新时间戳和最后时间戳不一致，序列号归零
        if (currentTimeStamp != lastTimeStamp)
            sequenceID = 0;

            //在更新时间后，如果在同一单位时间内，则递增序列号
        else {
            sequenceID = ++sequenceID & maxSequenceID;
            //如果递增序列号后归零，则表示2^12个ID已用完，进入阻塞模块等待更新时间
            if (sequenceID == 0)
                currentTimeStamp = blockUntilNextMS();
        }
        //更新最后时间戳
        lastTimeStamp = currentTimeStamp;
        //拼接63位的ID并返回
        return (lastTimeStamp << (zoneBits + machineBits + sequenceBits))
                | (zoneID << (machineBits + sequenceBits))
                | (machineID << sequenceBits)
                | (sequenceID);
    }
}
